/**
 * This file is part of Sonedyan.
 * 
 * Sonedyan is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation;
 * either version 3 of the License, or (at your option) any
 * later version.
 *
 * Sonedyan is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public
 * License along with Octave; see the file COPYING.  If not
 * see <http://www.gnu.org/licenses/>.
 * 
 * Copyright (C) 2009-2012 Jimmy Dubuisson <jimmy.dubuisson@gmail.com>
 */

package org.unige.mpej.eckmann.sonedyan.utils;

import java.util.Iterator;
import java.util.Vector;

import org.apache.log4j.Logger;

/**
 * Statistics methods
 */
public class Statistics
{
	private static Logger log = Logger.getLogger(org.unige.mpej.eckmann.sonedyan.utils.Statistics.class);
	
	/**
	 * compute mean
	 * @throws Exception 
	 */
	public static double sampleMean(Vector<Double> series) throws Exception
	{
		if (series != null && series.size() > 0)
		{
			double total = 0;
			
			for (Double value : series)
			{
				total += value;
			}
			
			return (total / series.size()); 
		}
		else
		{
			throw new Exception("An empty series was provided");
		}
	}
	
	/**
	 * compute sum
	 * @throws Exception 
	 */
	public static double sum(Vector<Double> series) throws Exception
	{
		if (series != null && series.size() > 0)
		{
			double total = 0;
			
			for (Double value : series)
			{
				total += value;
			}
			
			return total; 
		}
		else
		{
			throw new Exception("An empty series was provided");
		}
	}
	
	/**
	 * compute sum of powers
	 * @throws Exception 
	 */
	public static double sumOfPowers(Vector<Double> series, int power) throws Exception
	{
		if (series != null && series.size() > 0)
		{
			double total = 0;
			
			for (Double value : series)
			{
				total += Math.pow(value, power);
			}
			
			return total; 
		}
		else
		{
			throw new Exception("An empty series was provided");
		}
	}
	
	/**
	 * compute sum of power deviations
	 * @throws Exception 
	 */
	public static double sumOfPowerDeviations(Vector<Double> series, int power, double constant) throws Exception
	{
		if (series != null && series.size() > 0)
		{
			double total = 0;
			
			for (Double value : series)
			{
				total += Math.pow(value - constant, power);
			}
			
			return total; 
		}
		else
		{
			throw new Exception("An empty series was provided");
		}
	}
	
	/**
	 * compute variance
	 * 
	 * http://mathworld.wolfram.com/Variance.html
	 * @throws Exception 
	 */
	public static double variance(Vector<Double> series, double mean) throws Exception
	{
		if (series != null && series.size() > 0)
		{
			double total = sumOfPowers(series, 2);
			
			return ((total - Math.pow(mean, 2)) / series.size()); 
		}
		else
		{
			throw new Exception("An empty series was provided");
		}
	}
	
	/**
	 * compute unbiased ("bias corrected") sample variance
	 * 
	 * http://mathworld.wolfram.com/Variance.html
	 * @throws Exception 
	 */
	public static double sampleVariance(Vector<Double> series) throws Exception
	{
		if (series != null && series.size() > 0)
		{
			double total = sumOfPowers(series, 2);
			
			return ((total - Math.pow(sampleMean(series), 2)) / (series.size() - 1)); 
		}
		else
		{
			throw new Exception("An empty series was provided");
		}
	}
	
	/**
	 * compute variance (from specified standard deviation)
	 */
	public static double variance(double standardDeviation)
	{
		return Math.pow(standardDeviation, 2);
	}
	
	/**
	 * compute standard deviation
	 * @throws Exception 
	 */
	public static double standardDeviation(Vector<Double> series, double mean) throws Exception
	{
		return Math.sqrt(variance(series, mean));
	}
	
	/**
	 * compute sample standard deviation
	 * @throws Exception 
	 */
	public static double sampleStandardDeviation(Vector<Double> series) throws Exception
	{
		return Math.sqrt(sampleVariance(series));
	}
	
	/**
	 * compute standard deviation (from specified variance)
	 * @throws Exception 
	 */
	public static double standardDeviation(double variance) throws Exception
	{
		if (variance >= 0)
		{
			return Math.sqrt(variance);
		}
		else
		{
			throw new Exception("Negative variance was provided");
		}
	}
	
	/**
	 * compute covariance (see Wolfram )
	 * 
	 * http://mathworld.wolfram.com/Covariance.html
	 * @throws Exception 
	 */
	public static double covariance(Vector<Double> series1, Vector<Double> series2, double mean1, double mean2) throws Exception
	{
		if (series1 != null && series1.size() > 0 && series2 != null && series2.size() > 0 && series1.size() == series2.size())
		{	
			double total = 0;
			
			Iterator<Double> iterator1 = series1.iterator();
			Iterator<Double> iterator2 = series2.iterator();
			
			while (iterator1.hasNext())
			{
				Double v1 = iterator1.next();
				Double v2 = iterator2.next();
				
				total += (v1 - mean1) * (v2 - mean2);
			}
			
			return (total / series1.size()); 
		}
		else
		{
			throw new Exception("Invalid arguments were provided");
		}
	}
	
	/**
	 * compute covariance
	 * 
	 * http://mathworld.wolfram.com/Covariance.html
	 * http://www.ds.unifi.it/VL/VL_EN/sample/sample9.html
	 * @throws Exception 
	 */
	public static double sampleCovariance(Vector<Double> series1, Vector<Double> series2) throws Exception
	{
		if (series1 != null && series1.size() > 0 && series2 != null && series2.size() > 0 && series1.size() == series2.size())
		{	
			double sampleMean1 = sampleMean(series1);
			double sampleMean2 = sampleMean(series2);
			
			double total = 0;
			
			Iterator<Double> iterator1 = series1.iterator();
			Iterator<Double> iterator2 = series2.iterator();
			
			while (iterator1.hasNext())
			{
				Double v1 = iterator1.next();
				Double v2 = iterator2.next();
				
				total += (v1 - sampleMean1) * (v2 - sampleMean2);
			}
			
			return (total / (series1.size() - 1)); 
		}
		else
		{
			throw new Exception("Invalid arguments were provided");
		}
	}
	
	/**
	 * compute pearson correlation
	 * 
	 * http://en.wikipedia.org/wiki/Correlation_and_dependence
	 * http://www.ds.unifi.it/VL/VL_EN/sample/sample9.html
	 * @throws Exception 
	 */
	public static double sampleCorrelation(Vector<Double> series1, Vector<Double> series2) throws Exception
	{
		if (series1 != null && series1.size() > 0 && series2 != null && series2.size() > 0 && series1.size() == series2.size())
		{	
			double sampleSDev1 = sampleStandardDeviation(series1);
			double sampleSDev2 = sampleStandardDeviation(series2);
			
			return (sampleCovariance(series1, series2) / (sampleSDev1 * sampleSDev2));
		}
		else
		{
			throw new Exception("Invalid arguments were provided");
		}
	}
	
	/**
	 * compute sample autocovariance
	 * 
	 * http://www.stat.tamu.edu/~genton/1999.G.SPL.pdf
	 * http://staff.washington.edu/dbp/PDFFILES/three-curiosities.pdf
	 * @throws Exception 
	 */
	public static double sampleAutoCovariance(Vector<Double> series, int lag) throws Exception
	{
		if (series != null && series.size() > 0)
		{
			int size = series.size();
			
			if (lag >= 0 && lag < size)
			{
				double total = 0;
				double sampleMean = sampleMean(series);
				
				Double[] doubleArray = new Double[size];
				series.toArray(doubleArray);
				
				for (int j = 0; j < (size - lag); j++)
				{
					total += (doubleArray[j + lag] - sampleMean) * (doubleArray[j] - sampleMean);
				}
				
				return (total / (size - lag));
			}
			else
			{
				throw new Exception("Invalid lag value");
			}
		}
		else
		{
			throw new Exception("An empty series was provided");
		}
	}
	
	/**
	 * compute sample autocorrelation
	 * @throws Exception 
	 */
	public static double sampleAutoCorrelation(Vector<Double> series, int lag) throws Exception
	{
		if (series != null && series.size() > 0)
		{
			int size = series.size();
			
			if (lag >= 0 && lag < size)
			{
				return (sampleAutoCovariance(series, lag) / sampleVariance(series));
			}
			else
			{
				throw new Exception("Invalid lag value");
			}
		}
		else
		{
			throw new Exception("An empty series was provided");
		}
	}
	
	/**
	 * compute sample cross correlation
	 * @throws Exception 
	 */
	public static double sampleCrossCorrelation(Vector<Double> series1, Vector<Double> series2, int lag) throws Exception
	{
		if (series1 != null && series1.size() > 0 && series2 != null && series2.size() > 0 && series1.size() == series2.size())
		{
			int size = series1.size();
			
			if (lag >= 0 && lag < size)
			{
				double total = 0;
				
				double sampleMean1 = sampleMean(series1);
				double sampleMean2 = sampleMean(series2);
				
				double sampleVariance1 = sampleVariance(series1);
				double sampleVariance2 = sampleVariance(series2);
				
				Double[] doubleArray1 = new Double[size];
				series1.toArray(doubleArray1);
				
				Double[] doubleArray2 = new Double[size];
				series2.toArray(doubleArray2);
				
				for (int j = 0; j < (size - lag); j++)
				{
					total += (doubleArray1[j] - sampleMean1) * (doubleArray2[j + lag] - sampleMean2);
				}
				
				return (total * (1 / (size - lag)) * (1 / (sampleVariance1 * sampleVariance2)));
			}
			else
			{
				throw new Exception("Invalid lag value");
			}
		}
		else
		{
			throw new Exception("Invalid arguments were provided");
		}
	}
	
}
